﻿using System;
using System.Collections.Generic;
using System.Text;
using SCG.General;
using SCG.SolarVengeanceEngine.StarShips;
using SCG.SolarVengeanceEngine.BrainInfo;

namespace SCG.SolarVengeanceEngine
{
    //Base class for StarShip types in SV6
    public abstract class StarShip : SVMapObject, IComparer<StarShip>
    {
        //static constructor
        static StarShip()
        {         
            //load enumerated StarShipTypes into a list
            ShipTypes.Add(StarShipType.WarShip);
            ShipTypes.Add(StarShipType.ScanShip);
            ShipTypes.Add(StarShipType.Drone);
            ShipTypes.Add(StarShipType.EMine);
            ShipTypes.Add(StarShipType.SpyShip);
            ShipTypes.Add(StarShipType.Freighter);
            ShipTypes.Add(StarShipType.InkSpot);
            ShipTypes.Add(StarShipType.CloakDetector);
            ShipTypes.Add(StarShipType.Tug);
            ShipTypes.Add(StarShipType.HijackShip);
            ShipTypes.Add(StarShipType.ShieldGenerator);
            ShipTypes.Add(StarShipType.CommandShip);
            ShipTypes.Add(StarShipType.DoppleShip);
            ShipTypes.Add(StarShipType.RepairShip);
            ShipTypes.Add(StarShipType.DoomMissile);
            ShipTypes.Add(StarShipType.NukeMissile);
            ShipTypes.Add(StarShipType.MineSweeper);
            ShipTypes.Add(StarShipType.GravShip);
            ShipTypes.Add(StarShipType.StarTillery);
            ShipTypes.Add(StarShipType.TechShip);
            ShipTypes.Add(StarShipType.JumpGateShip);
            ShipTypes.Add(StarShipType.BattleStation);
            ShipTypes.Add(StarShipType.BeamShip);
            ShipTypes.Add(StarShipType.MadBomb);
            ShipTypes.Add(StarShipType.Elite);
            ShipTypes.Add(StarShipType.ParalyShip);
            ShipTypes.Add(StarShipType.RemoteViewer);
            ShipTypes.Add(StarShipType.Director);
            ShipTypes.Add(StarShipType.Carrier);
            ShipTypes.Add(StarShipType.Refinery);
            ShipTypes.Add(StarShipType.Zealot);
        }

        //provide easy access to a list of StarShipTypes
        public static List<StarShipType> ShipTypes = new List<StarShipType>();

        //Create an instance of a particular StarShip type
        public static StarShip CreateInstance(SVGame game, Player owner, StarShipType st, int engines, int extra, StarSystem origin)
        {
            switch (st)
            {
                case StarShipType.WarShip:
                    return new WarShip(game, owner, engines, extra, origin);
                case StarShipType.ScanShip:
                    return new ScanShip(game, owner, engines, extra, origin);
                case StarShipType.Drone:
                    return new Drone(game, owner, engines, origin);
                case StarShipType.EMine:
                    return new EMine(game, owner, origin);
                case StarShipType.SpyShip:
                    return new SpyShip(game, owner, engines, extra, origin);
                case StarShipType.Freighter:
                    return new Freighter(game, owner, engines, extra, origin);
                case StarShipType.InkSpot:
                    return new InkSpot(game, owner, engines, origin);
                case StarShipType.CloakDetector:
                    return new CloakDetector(game, owner, engines, extra, origin);
                case StarShipType.Tug:
                    return new Tug(game, owner, engines, origin);
                case StarShipType.HijackShip:
                    return new HijackShip(game, owner, engines, origin);
                case StarShipType.ShieldGenerator:
                    return new ShieldGenerator(game, owner, engines, origin);
                case StarShipType.CommandShip:
                    return new CommandShip(game, owner, engines, extra, origin);
                case StarShipType.DoppleShip:
                    return new DoppleShip(game, owner, engines, extra, origin);
                case StarShipType.RepairShip:
                    return new RepairShip(game, owner, engines, origin);
                case StarShipType.DoomMissile:
                    return new DoomMissile(game, owner, engines, extra, origin);
                case StarShipType.NukeMissile:
                    return new NukeMissile(game, owner, engines, extra, origin);
                case StarShipType.MineSweeper:
                    return new MineSweeper(game, owner, engines, extra, origin);
                case StarShipType.GravShip:
                    return new GravShip(game, owner, engines, origin);
                case StarShipType.StarTillery:
                    return new StarTillery(game, owner, engines, extra, origin);
                case StarShipType.TechShip:
                    return new TechShip(game, owner, origin);
                case StarShipType.JumpGateShip:
                    return new JumpGateShip(game, owner, engines, origin);
                case StarShipType.BattleStation:
                    return new BattleStation(game, owner, extra, origin);
                case StarShipType.BeamShip:
                    return new BeamShip(game, owner, engines, extra, origin);
                case StarShipType.MadBomb:
                    return new MadBomb(game, owner, engines, extra, origin);
                case StarShipType.Elite:
                    return new Elite(game, owner, engines, extra, origin);
                case StarShipType.ParalyShip:
                    return new ParalyShip(game, owner, engines, extra, origin);
                case StarShipType.RemoteViewer:
                    return new RemoteViewer(game, owner, extra, origin);
                case StarShipType.Director:
                    return new Director(game, owner, engines, extra, origin);
                case StarShipType.Carrier:
                    return new Carrier(game, owner, engines, extra, origin);
                case StarShipType.Refinery:
                    return new Refinery(game, owner, origin);
                case StarShipType.Zealot:
                    return new Zealot(game, owner, engines, extra, origin);
                default:
                    throw new ArgumentException("Unknown StarShip type: " + st);
            }
        }

        //Create an instance based on a persisted token
        public static StarShip CreateInstance(SVGame game, string token)
        {
            Tokenizer tok = new Tokenizer(token);
            string header = tok.TokenString;
            string content = tok.TokenString;

            //get header elements
            SVTokenizer tokHeader = new SVTokenizer(game, header);
            Player owner = tokHeader.TokenPlayer;
            StarShipType st = tokHeader.TokenStarShipType;
            int engines = tokHeader.TokenInt;
            int primary = tokHeader.TokenInt;       
            StarSystem origin = tokHeader.TokenStarSystem;

            //create the appropriate instance
            StarShip ship = CreateInstance(game, owner, st, engines, primary, origin);
            ship.Parse(content);
            return ship;
        }

        //constructor    
        public StarShip(SVGame game, Player owner, int engines, int extra, StarSystem origin)
            : base(game)
        {
            Owner = owner;
            OriginStarSystem = origin;
            Value = extra;
            MaxValue = extra;
            Engines = engines;
            MaxEngines = engines;
            AttackAdjacent = AttackAdjacentDefault;
            if (Owner != null)
                DiscoveredBy[Owner] = true;
     
            //Book keeping for Game object
            if (Game != null)
            {
                //add to master list
                lock (Game.StarShips)
                {
                    Game.StarShips.Add(this);
                }

                //Initialize tagged to false
                foreach (Player p in Game.Players)
                    _taggedBy[p] = false;
            }
        }

        //Remove it from the game
        public override void Remove()
        {
            base.Remove();
            if (Game != null)
            {
                //Locked in loop when removing from SVGame
                Game.StarShips.Remove(this);
                if (Owner != null)
                {
                    lock (Owner.StarShips)
                    {
                        Owner.StarShips.Remove(this);
                    }
                }

                //clear UDO's carried
                for (int n = UDOs.Count - 1; n >= 0; n--)
                    UDOs[n].Carrier = null;
            }
        }

        //This tag holds the visual element representing a StarShip's order
        public object OrderTag { get; set; }

        //This flag determines if the StarShip's action is prohibited this turn
        public bool ProhibitAction { get; set; }

        //This flag determines if the StarShip's movement is prohibited this turn
        public bool ProhibitMove { get; set; }

        //allow patrols into its space
        public override bool CanPatrolInto
        {
            get
            {
                return true;
            }
        }

        //facing
        private int _facing = 2;
        public int Facing
        {
            get
            {
                return _facing;
            }
            set
            {
                if (value >= 0 && value < 8)
                    _facing = value;
            }
        }

        //StarShip API - for derived classes to override
        #region StarShipAPI

        //Return the display name of the StarShip
        public abstract string Name
        {
            get;
        }

        //Return the StarShip type
        public abstract StarShipType ShipType
        {
            get;
        }

        //descriptive text
        public abstract string Description
        {
            get;
        }

        //Return the scan radius, default is 1 (adjacent objects only)
        public virtual int Scanners
        {
            get
            {
                return 1;
            }
        }

        //should the StarShip's Value be drawn as a visual circle? (Scanners, etc)
        public virtual SCGColor RadiusColor
        {
            get
            {
                return SCGColor.Transparent;
            }
        }
        public virtual bool DrawVisibleRange
        {
            get
            {
                return RadiusColor != SCGColor.Transparent;
            }
        }

        //Can this ship "spy"? (Detect type and details of revealed enemy
        public virtual bool CanSpy
        {
            get
            {
                return false;
            }
        }

        //Can the ship conquer starsystems?
        public virtual bool CanConquerStarSystems
        {
            get
            {
                return false;
            }
        }

        //Can the ship target the specific StarSystem?
        public virtual bool CanTargetStarSystem(StarSystem target, bool considerAlliance)
        {
            if (!CanExecuteOrder(OrderType.TargetStarSystem))
                return false;
            if (!target.VisibleTo[Owner])
                return true;
            if (target.Owner == null)
                return true;
            if (target.Owner == Owner)
                return false;
            if (!considerAlliance)
                return true;
            return !Owner.FriendlyTo(target.Owner);     
        }

        //Can the ship target a specific StarShip
        public virtual bool CanTargetStarShip(StarShip ship, bool respectAlliance)
        {
            return false;
        }

        //can it execute the specified order?
        public virtual bool CanExecuteOrder(OrderType ot)
        {
            if (IsEmbarked)
                return false;
            switch (ot)
            {
                case OrderType.Move:
                case OrderType.Escort:
                case OrderType.Patrol:
                case OrderType.BoardCarrier:
                    return true;
                case OrderType.ClearOrders:
                    return Order != null;
                case OrderType.EjectUDO:
                    return UDOs.Count > 0;
                default:
                    return false;
            }
        }

        //Eject UDOs
        public void EjectUDOs()
        {
            for (int n = UDOs.Count - 1; n >= 0; n--)
            {
                UDO udo = UDOs[n];
                SCGPoint ejectPt = Game.ClosestEmptyPoint(Location);
                udo.Carrier = null;
                udo.SetLocationSafely((int)ejectPt.X, (int)ejectPt.Y);
                if (Game.Host != null)
                    Game.Host.ObjectMoved(udo, udo.X, udo.Y, 20);              
            }
        }

        //Additional persistence data for subclasses
        protected virtual string SubClassPersist()
        {
            return "";
        }
        protected virtual void SubClassParse(SVTokenizer tok)
        {
        }

        //Perform special visibility processing
        protected internal virtual void VisibilityProcessing()
        {
        }

        //Initialization
        protected internal virtual void Initialize()
        {
        }

        //Perform common pre-processing
        protected internal virtual void CommonPreProcessing()
        {
        }

        //Perform pre-processing
        protected internal virtual void PreMoveProcessing()
        {
        }

        //perform special processing in movement action
        protected internal virtual bool MoveProcessing()
        {
            return false;
        }

        //Perform post-processing
        protected internal virtual void CommonPostProcessing()
        {
        }

        //Perform special move check logic (evasive, etc...)
        protected internal virtual bool CanMoveTo(int x, int y)
        {
            return true;
        }

        //Perform logic when targeting a StarSystem
        protected internal virtual void TargetStarSystem(StarSystem system)
        {
            if (!CanTargetStarSystem(system, false))
                Order = null;
            else if (IsAdjacent(system))
                TargetAdjacentStarSystem(system);
            else
                Game.MoveShipTowardPoint(this, system.Location);
        }

        //Perform logic when targeting an adjacent StarSystem
        protected virtual void TargetAdjacentStarSystem(StarSystem system)
        {
        }

        //Perform the logic of targeting a StarShip
        protected internal virtual void TargetStarShip(StarShip target)
        {
            if (target.VisibleTo[Owner] && !target.Destroyed)
            {
                if (!IsAdjacent(target))
                    Game.MoveShipTowardPoint(this, target.Location);
                else
                    TargetAdjacentStarShip(target);
            }
        }

        //Perform logic when targeting an adjacent starship
        protected virtual void TargetAdjacentStarShip(StarShip target)
        {
        }

        //Perform logic when targeting an adjacent map object
        protected internal virtual void TargetAdjacentMapObject(SVMapObject svm)
        {
        }

        //Normal damage processing, ship types can specialize
        protected internal virtual void ProcessDamage(int damage, bool canDamageEngines)
        {       
            //Determine if engines or primary system is damaged
            if (Value == 0)
                DamageEngines(damage);
            else if (canDamageEngines && SVGame.RNG.Next(Engines + Value) < Engines)
                DamageEngines(damage);
            else
                DamageExtra(damage);
        }

        //Does the starship think it can destroy the enemy?
        public virtual bool CanDestroy(StarShip target)
        {
            return false;
        }

        //special logic to perform when ship goes into a wormhole
        protected internal virtual void WormholeTraveled()
        {
        }

        //special logic to perform after each move
        protected internal virtual void PostMove(int oldX, int oldY)
        {
        }

        #endregion

        //Allow customization of game piece stat marking
        public virtual string EngineMark
        {
            get
            {
                return "E" + Engines.ToString();
            }
        }
        public virtual string PrimarySystemMark
        {
            get
            {
                if (!DiscoveredByCurrentPlayer)
                    return "";
                if (ShipType.GetPrimarySystemName() == "")
                    return "";
                else
                    return ShipType.GetPrimarySystemAbbreviation() + Value;
            }
        }
        public virtual string EngineStatLabel
        {
            get
            {
                if (Engines < MaxEngines)
                    return Engines + "/" + MaxEngines;
                else
                    return Engines.ToString();
            }
        }
        public virtual string PrimarySystemStatLabel
        {
            get
            {
                if (Value < MaxValue)
                    return Value + "/" + MaxValue;
                else
                    return Value.ToString();
            }
        }

        //Return the value of the primary system
        public int Value
        {
            get
            {
                //Return zero so damage processing will be correct for ships that don't have systems
                if (ShipType.GetPrimarySystemName() == "")
                    return 0;
                else
                    return _value;
            }
            set
            {
                if (value < 0)
                    value = 0;
                _value = value;
            }
        }
        public int MaxValue
        {
            get
            {
                return _maxValue;
            }
            set
            {
                if (value > 0)
                    _maxValue = value;
            }
        }

        //Number of engines
        public int Engines
        {
            get
            {
                return _engines;
            }
            set
            {
                if (value < 0)
                    value = 0;
                if (value > 20)
                    value = 20;
                _engines = value;
            }
        }
        public int MaxEngines
        {
            get
            {
                return _maxEngines;
            }
            set
            {
                if (value > 0 && value <= 20)
                    _maxEngines = value;
            }
        }

        //Allow ship types to modify their effective engines
        public virtual int EffectiveEngines
        {
            get
            {
                return Engines;
            }
        }

        //The Player who owns the StarShip      
        public Player Owner
        {
            get
            {
                return _owner;
            }
            set
            {
                //allow re-assignment of owner (including null owner)
                if (_owner != null)
                {
                    lock (_owner.StarShips)
                    {
                        _owner.StarShips.Remove(this);
                    }   
                }
                _owner = value;
                if (_owner != null)
                {
                    DiscoveredBy[_owner] = true;
                    lock (_owner.StarShips)
                    {
                        _owner.StarShips.Add(this);
                    }
                }
            }
        }

        //owned by current player?
        public bool OwnedByCurrentPlayer
        {
            get
            {
                if (Game == null)
                    return true;
                else
                    return Owner == Game.CurrentPlayer;
            }
        }

        //Discovered by current player?
        public override bool DiscoveredByCurrentPlayer
        {
            get
            {
                if (Game.CurrentPlayer == null)
                    return true;
                else if (Game.CurrentPlayer == Owner)
                    return true;
                else
                    return DiscoveredBy[Game.CurrentPlayer];
            }
        }

        //Is the ship cloaked?
        public bool Cloaked { get; set; }

        //Is it set to attack adjacent starships?
        public bool AttackAdjacent { get; set; }

        //The StarSystem where this StarShip was built (for JumpGate purposes)
        public StarSystem OriginStarSystem { get; set; }

        //Was the ship tagged by a specific player?
        public Dictionary<Player, bool> TaggedBy
        {
            get
            {
                return _taggedBy;
            }
        }

        //Is attack adjacent option on by default
        public virtual bool AttackAdjacentDefault
        {
            get
            {
                return false;
            }
        }

        //Is it damaged?
        public bool IsDamaged
        {
            get
            {
                return Engines < MaxEngines || Value < MaxValue;
            }
        }

        //Health rating - 1 to 100%
        public int Health
        {
            get
            {
                if (Engines == MaxEngines && Value == MaxValue)
                    return 100;
                int max = MaxEngines + MaxValue;
                int current = Engines + Value;
                if (max <= 0)
                    return 0;
                return (int)((current * 100) / max);
            }
        }

        //2-3 character code identifying a StarShip (legacy code)
        public string Code { get; set; }

        //The UDOs that the StarShip is carrying
        public List<UDO> UDOs
        {
            get
            {
                return _udos;
            }
        }

        //Move the UDOs when the starship is moved
        public override void SetLocation(int x, int y)
        {
            base.SetLocation(x, y);

            //move along UDOs that are being carried
            foreach (UDO udo in UDOs)
            {
                if (udo.X != x || udo.Y != y)
                    Game.MoveUDO(udo, x, y);           
            }

            //pick up new UDOs
            foreach (UDO udo in Game.UDOs)
                if (udo.AutoPickUp)
                    if (udo.X == x && udo.Y == y)
                        if (!UDOs.Contains(udo))
                            udo.Carrier = this;
        }

        //The ship's current orders     
        public StarShipOrder Order
        {
            get
            {
                return _order;
            }
            set
            {            
                //assign the new order
                _order = value;
                if (value == null)
                {
                    if (Game.Host != null)
                        Game.Host.StarShipOrderCleared(this);
                    Game.SubmitStateChange(StateChange.ClearOrder, this);
                }
                else 
                {
                    if (Game.Host != null)
                        Game.Host.StarShipOrderAssigned(this);
                    Game.SubmitStateChange(StateChange.AssignOrder, this, value);
                }
            }
        }

        //Is it destroyed?
        private bool _destroyed = false;
        public bool Destroyed
        {
            get
            {
                return _destroyed;
            }
            set
            {
                _destroyed = value;
                if (_destroyed)
                    ProcessDestruction();
            }
        }

        //is it embarked on a Carrier?
        public bool IsEmbarked { get; set; }

        //process destruction
        protected internal virtual void ProcessDestruction()
        {
        }

        //Damage engines or primary system
        protected void DamageEngines(int damage)
        {
            Engines -= damage;
            if (Engines <= 0)
            {
                Engines = 0;
                Game.DestroyStarShip(this);
            }
        }
        protected void DamageExtra(int damage)
        {
            Value -= damage;
            if (Value <= 0)
            {
                Value = 0;
                Game.DestroyStarShip(this);
            }
        }

        //Persistence
        public override string Persist()
        {
            //create header item that allows subclasses to be created
            SVTokenizer tok = new SVTokenizer(Game);
            tok.Tokenize(Owner, ShipType, Engines, Value, OriginStarSystem);
            string header = tok.Result;

            //normal persistence
            tok = new SVTokenizer(Game);
            tok.Tokenize(Cloaked, MaxEngines, MaxValue, Code, AttackAdjacent, Order);
            foreach (Player p in Game.Players)
                tok.AppendToken(TaggedBy[p]);
            tok.AppendToken(SubClassPersist());
            string content = base.Persist() + tok.Result;

            //return the combined header and content
            Tokenizer combined = new Tokenizer();
            combined.Tokenize(header, content);
            return combined.Result;
        }
        public override SVTokenizer Parse(string content)
        {
            SVTokenizer tok = base.Parse(content);
            Cloaked = tok.TokenBoolean;
            MaxEngines = tok.TokenInt;
            MaxValue = tok.TokenInt;
            Code = tok.TokenString;
            AttackAdjacent = tok.TokenBoolean;
            Order = tok.TokenStarShipOrder;
            foreach (Player p in Game.Players)
                TaggedBy[p] = tok.TokenBoolean;
            string subClassString = tok.TokenString;
            SVTokenizer subClassTok = new SVTokenizer(Game, subClassString);
            SubClassParse(subClassTok);
            return tok;
        }      

        //used to re-link referenced StarShips
        public virtual void Relink()
        {
        }

        //ToString
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(Name);
            sb.Append(" E");
            sb.Append(Engines);
            if (ShipType.GetPrimarySystemName() != "")
            {
                sb.Append(" ");
                sb.Append(ShipType.GetPrimarySystemAbbreviation());
                sb.Append(Value);
            }
            return sb.ToString();
        }

        //Allow StarShips to be sorted
        #region IComparer<StarShip> Members

        public int Compare(StarShip x, StarShip y)
        {
            if (x.ShipType == y.ShipType)
            {
                if (x.Code != null && y.Code != null)
                    return x.Code.CompareTo(y.Code);
                else
                    return 0;
            }
            else
                return x.ShipType.CompareTo(y.ShipType);
        }

        #endregion

        //private members      
        private Player _owner;
        private Dictionary<Player, bool> _taggedBy = new Dictionary<Player, bool>();
        private int _value;
        private int _maxValue;
        private int _engines;
        private int _maxEngines;
        private List<UDO> _udos = new List<UDO>();
        private StarShipOrder _order = null;
        private StarShipInfo _info = null;

        //Access info object for Brains
        internal StarShipInfo Info
        {
            get
            {
                if (_info == null)
                    _info = new StarShipInfo(this);
                return _info;
            }
        }

        //Damage a StarShip
        internal void Damage(int damage, bool canDamageEngines)
        {
            //process damage on a ship type basis
            ProcessDamage(damage, canDamageEngines);

            //report damage to host and clients
            if (!Destroyed)
            {
                if (Game.Host != null)
                    Game.Host.StarShipDamaged(this);
                Game.SubmitStateChange(StateChange.Damage, this, Engines, Value);
            }
        }
    }
}
